﻿package {
	import flash.display.*;
	import flash.events.*;
	import flash.utils.Timer
	import flash.ui.Keyboard;
	import flash.geom.Rectangle;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import org.papervision3d.core.*;
	import org.papervision3d.cameras.*;
	import org.papervision3d.materials.*;
	import org.papervision3d.objects.*;
	import org.papervision3d.scenes.*;
	import org.papervision3d.events.*;
	
	[SWF(width="400", height="480", backgroundColor="#FFFFFF")]
	public class Main extends Sprite {
		// Papervision3D 系
		private var container:Sprite;
		private var scene:Scene3D;
		private var rootNode:DisplayObject3D;
		private var camera:Camera3D;
		private var enemies:Array = new Array();
		private var bullets:Array = new Array();
		private var enemyBullets:Array = new Array();
		
		// 入力系
		private var keyRight:Boolean = false;
		private var keyLeft:Boolean = false;
		private var keyUp:Boolean = false;
		private var keyDown:Boolean = false;
		private var keyShoot:Boolean = false;
		private var keySlow:Boolean = false;
		private var shootWait:int = 0;
		
		// 文字出力
		private var tf:TextField;
		
		// ゲーム定数
		public static const FRAME_PERIOD:Number = 25;
		
		private function get player():DisplayObject3D {
			return rootNode.getChildByName("player");
		}
		
		private var _enemyBulletMaterial:BitmapMaterial;
		/**
		* 弾のマテリアル
		*/
		private function get enemyBulletMaterial():BitmapMaterial {
			if (_enemyBulletMaterial == null) {
				// テクスチャを作ります
				var tex:BitmapData = new BitmapData(8, 8);
				tex.fillRect(new Rectangle(0, 0, 8, 8), 0xffff0000);
				
				// マテリアルを作ります
				_enemyBulletMaterial = new BitmapMaterial(tex);
				_enemyBulletMaterial.oneSide = false;
			}
			return _enemyBulletMaterial;
		}
		
		private var _bulletMaterial:BitmapMaterial;
		/**
		* 敵の弾のマテリアル
		*/
		private function get bulletMaterial():BitmapMaterial {
			if (_bulletMaterial == null) {
				// 星を描きます
				var star:Shape = new Shape();
				star.graphics.beginFill(0xffff00);
				star.graphics.lineStyle(3, 0x000000);
				star.graphics.moveTo(64, 32);
				for (var i:int = 1; i < 11; ++i) {
					var r:Number = (i % 2 == 0) ? 32 : 16;
					star.graphics.lineTo(32 + r * Math.cos(i * 36 * Math.PI / 180),
						32 + r * Math.sin(i * 36 * Math.PI / 180));
				}
				star.graphics.endFill();
				
				// テクスチャを作ります
				var tex:BitmapData = new BitmapData(64, 64);
				tex.fillRect(new Rectangle(0, 0, 64, 64), 0x00000000);
				tex.draw(star);
				
				// マテリアルを作ります
				_bulletMaterial = new BitmapMaterial(tex);
				_bulletMaterial.oneSide = false;
			}
			return _bulletMaterial;
		}
		
		public function Main() {
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
			this.addEventListener(Event.ENTER_FRAME, loop3D);
			
			tf = new TextField();
			tf.width = 400;
			tf.height = 480;
			tf.selectable = false;
			addChild(tf);
			
			init3D();
		}
		
		private function init3D():void {
			// 新しいスプライトを作って表示リストに入れておく。
			container = new Sprite();
			container.x = 400 / 2;
			container.y = 480 / 2;
			addChild(container);
			
			// シーンを作る。
			scene = new Scene3D(container);
			
			// カメラを作る。
			camera = new Camera3D();
			camera.y = 5;
			camera.z = -2;
			camera.zoom = 300;
			camera.focus = 1;
			
			// 3D シーンにルートオブジェクトを作る。
			rootNode = scene.addChild(new DisplayObject3D("rootNode"));
			
			// 自機を作る。
			generatePlayer();
		}
		
		private function loop3D(evt:Event):void {
			// 低速キーを押しているかどうかで自機の移動量を設定します
			var moveDistance:Number = keySlow ? 0.03 : 0.1;
			
			// 自機の移動処理
			if (keyUp) {
				player.z += moveDistance;
			} else if (keyDown) {
				player.z -= moveDistance;
			}
			if (keyLeft) {
				player.x -= moveDistance;
			} else if (keyRight) {
				player.x += moveDistance;
			}
			
			// 弾を撃つ処理
			--shootWait;
			if (keyShoot && shootWait < 0) {
				generateBullet();
				shootWait = 5;
			}
			
			if (Math.random() < FRAME_PERIOD / 1000) {
				generateEnemy();
			}
			
			tf.text = "Arrow: Move\n" +
				"Z: Shoot\n" +
				"Shift: Slow\n";
			
			// シーンをレンダリングします
			scene.renderCamera(camera);
		}
		
		private function keyDownHandler(evt:KeyboardEvent):void {
			switch (evt.keyCode) {
			case Keyboard.UP: {
				keyUp = true;
				break; }
			case Keyboard.DOWN: {
				keyDown = true;
				break; }
			case Keyboard.LEFT: {
				keyLeft = true;
				break; }
			case Keyboard.RIGHT: {
				keyRight = true;
				break; }
			case "Z".charCodeAt(): {
				keyShoot = true;
				break; }
			case Keyboard.SHIFT: {
				keySlow = true;
				break; }
			case Keyboard.ESCAPE: {
				player.x = player.y = player.z = 0;
				break; }
			}
		}
		
		private function keyUpHandler(evt:KeyboardEvent):void {
			switch (evt.keyCode) {
			case Keyboard.UP: {
				keyUp = false;
				break; }
			case Keyboard.DOWN: {
				keyDown = false;
				break; }
			case Keyboard.LEFT: {
				keyLeft = false;
				break; }
			case Keyboard.RIGHT: {
				keyRight = false;
				break; }
			case "Z".charCodeAt(): {
				keyShoot = false;
				break; }
			case Keyboard.SHIFT: {
				keySlow = false;
				break; }
			}
		}
		
		/**
			プレイヤーを発生させます。
		*/
		private function generatePlayer():void {
			// テクスチャを作ります
			var tex:BitmapData = new BitmapData(8, 8);
			tex.fillRect(new Rectangle(0, 0, 8, 8), 0xff000000);
			tex.fillRect(new Rectangle(0, 3, 8, 1), 0xffffffff);
			
			// マテリアルを作ります
			var material:BitmapMaterial = new BitmapMaterial(tex);
			material.oneSide = false;
			
			// プレイヤーオブジェクト。
			var obj:DisplayObject3D = new Cylinder(
				material, // 白黒マテリアル
				0.2, // 半径 20cm
				1.0, // 長さ 1m
				Cylinder.DEFAULT_SEGMENTSW,
				Cylinder.DEFAULT_SEGMENTSH,
				0.00001); // 先端半径極小
			obj.pitch(90);
			rootNode.addChild(obj, "player");
		}
		
		/**
			弾を発生させます。
		*/
		private function generateBullet():void {
			// 弾オブジェクトを作ります
			var obj:DisplayObject3D = new Plane(bulletMaterial, 0.3, 0.3);
			
			// 位置をプレイヤーに合わせませう
			obj.x = player.x;
			obj.y = player.y;
			obj.z = player.z;
			
			// 向きを設定します
			obj.rotationX = 90;
			obj.rotationY = 10;
			
			// ルートノードに追加します
			rootNode.addChild(obj);
			
			bullets.push(obj);
			
			var timer:Timer = new Timer(FRAME_PERIOD, 300);
			timer.addEventListener(TimerEvent.TIMER, function(evt:TimerEvent):void {
				// フレーム単位の移動量。秒単位で 2
				var d:Number = 3.0 / evt.target.delay;
				// 弾を移動させます
				obj.x += d * Math.sin(obj.rotationY * Math.PI / 180);
				obj.z += d * Math.cos(obj.rotationY * Math.PI / 180);
				
				// 敵との衝突判定
				for each (var enemy:DisplayObject3D in enemies) {
					var dx:Number = obj.x - enemy.x;
					var dy:Number = obj.y - enemy.y;
					var dz:Number = obj.z - enemy.z;
					if (dx * dx + dy * dy + dz * dz < 0.3) {
						timer.repeatCount = timer.currentCount;
						enemy.z = 10.0;
						break;
					}
				}
			});
			timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(evt:Event):void {
				rootNode.removeChild(obj);
				bullets.splice(bullets.indexOf(obj), 1);
			});
			timer.start();
		}
		
		/**
			敵を発生させます。
		*/
		private function generateEnemy():void {
			// テクスチャを作ります
			var tex:BitmapData = new BitmapData(8, 8);
			tex.fillRect(new Rectangle(0, 0, 8, 8), 0xff000000);
			tex.fillRect(new Rectangle(1, 1, 6, 6), 0xffff0000);
			
			// マテリアルを作ります
			var material:BitmapMaterial = new BitmapMaterial(tex);
			material.oneSide = false;
			
			// オブジェクトを作ります
			var obj:DisplayObject3D = new Cube(material, 0.5, 0.5, 0.5);
			
			// 位置をプレイヤーに合わせませう
			obj.x = 8 * (Math.random() - 0.5);
			obj.y = player.y;
			obj.z = 10.0;
			
			// ルートノードに追加します
			rootNode.addChild(obj);
			
			enemies.push(obj);
			
			var timer:Timer = new Timer(FRAME_PERIOD, 0);
			timer.addEventListener(TimerEvent.TIMER, function(evt:TimerEvent):void {
				// フレーム単位の移動量
				var d:Number = 3.0 / evt.target.delay;
				// 移動させます
				obj.z -= d;
				obj.rotationY += 10;
				// 弾を撃つ
				if (Math.random() < 0.1) {
					generateEnemyBullet(obj);
				}
				// 消去処理
				if (obj.z <= -2.0) {
					timer.stop();
					rootNode.removeChild(obj);
					enemies.splice(enemies.indexOf(obj), 1);
				}
			});
			timer.start();
		}
		
		private function generateEnemyBullet(source:DisplayObject3D):void {
			// オブジェクトを作ります
			var obj:DisplayObject3D = new Plane(enemyBulletMaterial, 0.1, 0.1);
			//var obj:DisplayObject3D = new Cube(enemyBulletMaterial, 0.1, 0.1, 0.1);
			
			// 位置を敵に合わせる
			obj.x = source.x;
			obj.y = source.y;
			obj.z = source.z;
			obj.rotationX = 90;
			obj.rotationY = 180 + 60 * (Math.random() - 0.5);
			
			// ルートノードに追加します
			rootNode.addChild(obj);
			enemyBullets.push(obj);
			
			var timer:Timer = new Timer(FRAME_PERIOD, 300);
			timer.addEventListener(TimerEvent.TIMER, function(evt:TimerEvent):void {
				// フレーム単位の移動量。秒単位で 2
				var d:Number = 4.0 / evt.target.delay;
				// 弾を移動させます
				obj.x += d * Math.sin(obj.rotationY * Math.PI / 180);
				obj.z += d * Math.cos(obj.rotationY * Math.PI / 180);
			});
			timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(evt:Event):void {
				rootNode.removeChild(obj);
				enemyBullets.splice(enemyBullets.indexOf(obj), 1);
			});
			
			timer.start();
		}
	}
}
